#include <windows.h>
#include <string.h>

#include "lib.h"
#include "patch.h"
#include "rslistpkg.h"
#include "client.h"
#include "patchsrv.h"
#include "pkglist.h"
#include "pkgdesc.h"
#include "serializer.h"
#include "md4.h"

// Send list of files available in a package (send-only protocol)
//
// Request:
//
// <msg len><C><pkgname>
//
// Responses to this request are:
//
// <3><E><initial error>
//
//	or
//
// <3><C><# files>
//		<file>..<file>
//			or
//		<3><E><error after initial success>
//
// Descriptions:
//
// <initial error>
//		EM_ServerDownForMaintenance:  generated in DetermineRequest
//
//	<file>:  each encoded file name as follows:
//		<msg len byte><F><filename bytes>
//
//	<error after initial success>
//		EM_PkgListChanged:  	if # packages requires multiple writes and the
//								list changes between writes
//
// No other errors are possible.
//

RSListPackages::RSListPackages(Client* client)
	: Protocol(client)
{
	m_requested_pkg = 0;
	m_filename_tosend = 0;
	m_done = true;
	m_version = 0x00;
}

RSListPackages::~RSListPackages()
{
}

bool8
RSListPackages::recognize(uint8* Msg, uint32 Len)
{
	bool8 success;

	m_multibyte = false;
	success = (bool8)(Msg[0] == 'C' && Len > 1);
	if (!success)
	{
		success = (bool8)(((*(uint16*)&Msg[0]) == *(uint16*)"MC") && Len > 2);
		if (success)
			m_multibyte = true;
	}
	return success;
}

int32
RSListPackages::start(uint8 *msg, uint32 len)
{
	// firstly, let's find the package the client asked us for.
	PackageList* 		list;
	PackageDescription* item;

	int32 result = EM_UnknownPackage;

	list = PatchServer::This->getPackageList();
	if(list)
	{
		// just a sanity check
		if (len - 1 >= MAX_PATH)
		{
			len = MAX_PATH;
		}

		// for clarity, the len is the message len, not the str len.
		//  the str len is the message len minus one!


		if (m_multibyte)
		{
			// and extract the unicode filename
			// [AO 2007-10-19] Sanity check: do not exceed message size
			if (*reinterpret_cast<uint32*>(msg + 2) * sizeof (uint16) > (len - 2) - sizeof (uint32))
				m_filename.empty();
			else
				m_filename.deserialize(msg + 2);
		}
		else
		{
			// and extract the ASCII filename
			char* buf = new char[len];
			strncpy(buf, (char*)msg + 1, len - 1);
			buf[len - 1] = '\0';
			m_filename = buf;
			delete buf;
		}

		if (m_filename.caseCompare("Bank") == 0)
			m_filename = "Cashier";

		item = list->getItem(0);
		while(item && 0 != item->getName().caseCompare(m_filename))
		{
			item = item->getNext();
			m_requested_pkg++;
		}

		if(item)
		{
			// the first filename to send, so send the header first...
			// save the version to make sure it's not updated WHILE we're
			//  sending it...
			m_version = item->getVersion();

			client->SetPackage(item);

			m_done = false;

			DEBUGMSG(("Listing Files in Package: %S", (const uint16*)item->getName()));

			result = EM_Success;
		}	

		// be sure to unlock the package list!!!
		PatchServer::This->releasePackageList();
	}

	return result;
}

void
RSListPackages::netIn(int32 /*Amt*/)
{
	// start protocol...RSListPackages will not post a network read during
	// the lifetime of the protocol.
	//
	continueSend();
}

void
RSListPackages::netOut(int32 /*Amt*/)
{
	if (!m_done)
		continueSend();
	else
		client->protocolDone();
}

void
RSListPackages::continueSend()
{
	PackageList* list;
	PackageDescription* item;
	uint32 remain;
	uint8* obuf;

	list = PatchServer::This->getPackageList();
	if (!list)
	{
		// couldn't get the package list!
		m_done = true;
		client->errmsg(EM_RSPkgListChanged);	
		return;
	}

	// get the package that the client requested the contents of...
	item = list->getItem(m_requested_pkg);

	if(!item || m_filename.caseCompare(item->getName()))
	{
		// the order of the list must have changed while we're downloading
		//  it!  let's find our package again...
		item = list->getItem(0);
		m_requested_pkg = 0;
		while(item && 0 != m_filename.caseCompare(item->getName()))
		{
			item = item->getNext();
			m_requested_pkg++;
		}

		if(!item)
		{
			// the package the client is requesting is no longer suported!
			//  so pass that info along...
			PatchServer::This->releasePackageList();
			m_done = true;
			client->errmsg(EM_RSPkgListChanged);	
			return;
		}

		// we assume that only the order has changed.  if the contents
		//  have changed, then the version must be different.  so it
		//  seems sufficient to simply find it again.

		// update our client's poitner to the package description
		client->SetPackage(item);
	}

	if(m_version != item->getVersion())
	{
		// the package has been updated while we're downloading it!
		PatchServer::This->releasePackageList();
		m_done = true;
		client->errmsg(EM_RSPkgChanged);	
		return;
	}

	remain = client->getOSize();
	obuf = client->getOBuf();

	TCHAR* filenames = item->getContents();
	t_md4 *checksums = item->getStrongChecksums();
	if(!filenames || !checksums)
	{
		// this is an empty package?  can't be...
		PatchServer::This->releasePackageList();
		m_done = true;
		client->errmsg(EM_RSBadPackage);
		return;
	}

	if(m_filename_tosend == 0)
	{
		if(remain >= 8)
		{
			// now we've found the package the client is interested in
			//  so what we're gonna do is send a header message then
			//  follow it with the filenames of the contents of this package.

			// msg header
			//
			*obuf++ = (uint8)0x08;
			*obuf++ = 'C';
			*obuf++ = (uint8)0x01;	// rspkg list protocol version
			*obuf++ = (uint8)(item->getNumFiles() & 0xff);
			obuf += CSerializer::Wire(obuf, item->getUnzippedPkgSize());

			remain -= 8;
		}
		else
		{
			// the header must be sent first, and there's not enough
			//  space in the out buffer to send it, so just try again
			//  later...
			PatchServer::This->releasePackageList();
			client->writeNet();
			return;
		}
	}

	// now we wish to point our char * to the next filename to send...
	for(int32 i = 0; i < m_filename_tosend; i++)
	{
		if(wcslen(filenames))
			filenames += wcslen(filenames) + 1;
	}

	// while we're not done and there is still room on the buffer, keep
	// sending! (strlen(filenames) + 1) is the lengthof the name with the
	//  null terminator.  16 == 128 bits for the strong checksum.
	while(wcslen(filenames) && ((3 + 16 + 4 + wcslen(filenames)*2) <= remain))
	{
		uint8* plen = 0;

		// fill the message out...
		//  add one for the null terminator, two for the msg size and msgid
		//  byte, and another 16 for the md4 

		if (m_multibyte)
		{
			// [0]
			plen = obuf++;
			// [1]
			*obuf++ = (uint8)'M';
			// [2]
			*obuf++ = (uint8)'F';
		}
		else
		{
			// [0]
			plen = obuf++;
			// [1]
			*obuf++ = (uint8)'F';
		}

		// [3]
		obuf += CSerializer::Wire(obuf, checksums[m_filename_tosend][0]);
		// [7]
		obuf += CSerializer::Wire(obuf, checksums[m_filename_tosend][1]);
		// [11]
		obuf += CSerializer::Wire(obuf, checksums[m_filename_tosend][2]);
		// [15]
		obuf += CSerializer::Wire(obuf, checksums[m_filename_tosend][3]);

		// 19

		if (m_multibyte)
		{
			String fname = filenames;
			obuf += fname.serialize(obuf);
		}
		else
		{
			StringConversions::convert((char *)obuf, filenames, wcslen(filenames)+1);
			obuf += (wcslen(filenames) + 1);
		}

		*plen = obuf - plen;

		// filename			  2*filenamelen + 4 
		// msg id 							  3
		// strong checksum					 16
		//--------------------------------------
		//	                 2*filenamelen + 23

        ASSERT( remain >= *plen );
        remain -= *plen;

		m_filename_tosend++;
		filenames += (wcslen(filenames) + 1);
	}

	if(!wcslen(filenames) && (4 <= remain))
	{
		if (m_multibyte)
		{
			*obuf++ = (uint8)(0x04);
			*obuf++ = (uint8)'M';
			*obuf++ = (uint8)'F';
			*obuf++ = (uint8)(0x00);
			remain -= 4;
		}
		else
		{
			*obuf++ = (uint8)(0x03);
			*obuf++ = (uint8)'F';
			*obuf++ = (uint8)(0x00);
			remain -= 3;
		}

		// this was the last filename
		m_done = (bool8)(wcslen(filenames) == 0);
	}

	client->setOUsed(client->getOSize() - remain);

	PatchServer::This->releasePackageList();

	client->writeNet();
}



